﻿using System;
using System.Collections.Generic;
using System.Runtime.Serialization.Json;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Data;
using MSL.Utility;
using System.Text.RegularExpressions;
using System.Xml;

namespace DataBase.Core
{
    /*
      private static void TestJsonDatabase()
    {
        JsonDatabase db = new JsonDatabase(@"C:\tmp");
        db.OutputIndent = true;

        Cat origin = new Cat() { Name = "Garfield", Legs = 4 };
        db.Save<Cat>(origin);

        db.Save<Cat>(origin.Id, origin);
        db.Delete<Cat>(origin.Id);
    }
     */
    /// <summary>
    /// xml文件数据库
    /// </summary>
    public class XmlDatabase
    {
        #region Fields

        protected static readonly object operationLock = new object();//文件数据库操作锁
        private static HashSet<char> invalidFileNameChars;
        MSL.Utility.Doc.XmlHelper xml = null;

        static XmlDatabase()
        {
            invalidFileNameChars = new HashSet<char>() { '\0', ' ', '.', '$', '/', '\\' };
            foreach (var c in Path.GetInvalidPathChars()) { invalidFileNameChars.Add(c); }
            foreach (var c in Path.GetInvalidFileNameChars()) { invalidFileNameChars.Add(c); }
        }

        /// <summary>
        /// 文件数据库
        /// </summary>
        /// <param name="directory">数据库文件所在目录</param>
        protected XmlDatabase(string directory)
        {
            Directory = directory;
        }

        #endregion

        #region Properties

        /// <summary>
        /// 数据库文件所在目录
        /// </summary>
        public virtual string TableName { get; private set; }

        /// <summary>
        /// 数据库文件所在目录
        /// </summary>
        public virtual string Directory { get; private set; }

        /// <summary>
        /// 是否输出缩进
        /// </summary>
        public virtual bool OutputIndent { get; set; }

        /// <summary>
        /// 文件扩展名
        /// </summary>
        public virtual string FileExtension { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// 保存文档
        /// </summary>
        /// <typeparam name="TDocument">文档类型</typeparam>
        /// <param name="document">文档对象</param>
        /// <returns>文档ID</returns>
        public virtual string Save<TDocument>(TDocument document)
        {
            return Save<TDocument>(ObjectId.NewObjectId().ToString(), document);
        }

        /// <summary>
        /// 保存文档
        /// </summary>
        /// <typeparam name="TDocument">文档类型</typeparam>
        /// <param name="id">文档ID</param>
        /// <param name="document">文档对象</param>
        /// <returns>文档ID</returns>
        public virtual string Save<TDocument>(string id, TDocument document)
        {
            if (string.IsNullOrEmpty(id))
                throw new ArgumentNullException("id");

            if (document == null)
                throw new ArgumentNullException("document");

            Delete<TDocument>(id);

            try
            {
                string fileName = GenerateFilePath<TDocument>();//(id);
                string output = Serialize(document);

                lock (operationLock)
                {
                    System.IO.FileInfo info = new System.IO.FileInfo(fileName);
                    DirectoryInfo di = new DirectoryInfo(info.Directory.FullName);
                    if (!di.Exists)
                    {
                        di.Create();
                    }
                    output = ReadFile(fileName) + output;
                    System.IO.File.WriteAllText(fileName, output);
                }
            }
            catch (Exception ex)
            {
                throw new FileDBException(string.Format(CultureInfo.InvariantCulture, "Save document failed with id [{0}].", id), ex);
            }

            return id;
        }

        /// <summary>
        /// 根据文档ID查找文档
        /// </summary>
        /// <typeparam name="TDocument">文档类型</typeparam>
        /// <param name="id">文档ID</param>
        /// <returns>文档对象</returns>
        public virtual TDocument FindOneById<TDocument>(string id)
        {
            if (string.IsNullOrEmpty(id))
                throw new ArgumentNullException("id");
            /*
             <Artile id='1'>
             * 
             * </Article>
             */

            try
            {
                string fileName = GenerateFilePath<TDocument>();
                xml = new MSL.Utility.Doc.XmlHelper(fileName);
                if (File.Exists(fileName))
                {
                    List<XmlNode> list = xml.GetNodeList(typeof(TDocument).Name, "id", id);
                    if (list.Count > 0)
                    {
                        return Deserialize<TDocument>(list[0].InnerXml);
                    }
                }
                return default(TDocument);
            }
            catch (Exception ex)
            {
                throw new FileDBException(string.Format(CultureInfo.InvariantCulture, "Find document by id [{0}] failed.", id), ex);
            }
        }

        /// <summary>
        /// 查找指定类型的所有文档
        /// </summary>
        /// <typeparam name="TDocument">文档类型</typeparam>
        /// <returns>文档对象序列</returns>
        public virtual IEnumerable<TDocument> FindAll<TDocument>()
        {
            string fileName = GenerateFilePath<TDocument>();
            List<TDocument> list = new List<TDocument>();
            try
            {
                xml = new MSL.Utility.Doc.XmlHelper(fileName);
                if (File.Exists(fileName))
                {
                    List<XmlNode> nodes = xml.GetNodeList(typeof(TDocument).Name, "", "");
                    if (list.Count > 0)
                    {
                        foreach (XmlNode node in nodes)
                        {
                            list.Add(Deserialize<TDocument>(node.InnerXml));
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw new FileDBException("Find all documents failed.", ex);
            }
            return list;
        }

        /// <summary>
        /// 查找指定类型的所有文档
        /// </summary>
        /// <typeparam name="TDocument">文档类型</typeparam>
        /// <returns>文档对象序列</returns>
        public virtual IEnumerable<TDocument> FindAll<TDocument>(string where, string sortBy)
        {
            return null;
        }

        /// <summary>
        /// 根据指定文档ID删除文档
        /// </summary>
        /// <typeparam name="TDocument">文档类型</typeparam>
        /// <param name="id">文档ID</param>
        public virtual void Delete<TDocument>(string id)
        {
            if (string.IsNullOrEmpty(id))
                throw new ArgumentNullException("id");

            try
            {
                string fileName = GenerateFilePath<TDocument>();
                if (File.Exists(fileName))
                {
                    lock (operationLock)
                    {
                        xml = new MSL.Utility.Doc.XmlHelper(fileName);
                        xml.RemoveNodes(typeof(TDocument).Name, "id", id);
                        xml.Save();
                    }
                }
            }
            catch (Exception ex)
            {
                throw new FileDBException(string.Format(CultureInfo.InvariantCulture, "Delete document by id [{0}] failed.", id), ex);
            }
        }

        /// <summary>
        /// 删除所有指定类型的文档
        /// </summary>
        /// <typeparam name="TDocument">文档类型</typeparam>
        public virtual void DeleteAll<TDocument>()
        {
            try
            {
                string[] files = System.IO.Directory.GetFiles(
                  GenerateFilePath<TDocument>(), "*." + FileExtension,
                  SearchOption.TopDirectoryOnly);

                foreach (string fileName in files)
                {
                    lock (operationLock)
                    {
                        File.Delete(fileName);
                    }
                }
            }
            catch (Exception ex)
            {
                throw new FileDBException("Delete all documents failed.", ex);
            }
        }

        /// <summary>
        /// 获取指定类型文档的数量
        /// </summary>
        /// <typeparam name="TDocument">文档类型</typeparam>
        /// <returns>文档的数量</returns>
        public virtual int Count<TDocument>()
        {
            try
            {
                string[] files = System.IO.Directory.GetFiles(GenerateFilePath<TDocument>(), "*." + FileExtension, SearchOption.TopDirectoryOnly);
                if (files != null)
                {
                    return files.Length;
                }
                else
                {
                    return 0;
                }
            }
            catch (Exception ex)
            {
                throw new FileDBException("Count all documents failed.", ex);
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// 生成文件全路径
        /// </summary>
        /// <typeparam name="TDocument">文档类型</typeparam>
        /// <returns>文件路径</returns>
        protected virtual string GenerateFilePath<TDocument>()
        {
            return Path.Combine(this.Directory, typeof(TDocument).Name) + this.FileExtension;
        }
        /// <summary>
        /// 将指定的文档对象序列化至字符串
        /// </summary>
        /// <param name="value">指定的文档对象</param>
        /// <returns>文档对象序列化后的字符串</returns>
        protected string Serialize(object value)
        {
            DataContractJsonSerializer serializer = new DataContractJsonSerializer(value.GetType());
            using (MemoryStream ms = new MemoryStream())
            {
                serializer.WriteObject(ms, value);
                return Encoding.UTF8.GetString(ms.ToArray());
            }
            return "";
        }
        /// <summary>
        /// 将字符串反序列化成文档对象
        /// <typeparam name="TDocument">文档类型</typeparam>
        /// <param name="data">字符串</param>
        /// 文档对象
        protected virtual TDocument Deserialize<TDocument>(string data)
        {
            if (string.IsNullOrEmpty(data))
                throw new ArgumentNullException("data");

            using (StringReader sr = new StringReader(data))
            {
                System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(TDocument));
                return (TDocument)serializer.Deserialize(sr);
            }
        }
        /// <summary>
        /// 读取本地文件
        /// </summary>
        /// <param name="path">路径</param>
        /// <returns></returns>
        protected static string ReadFile(string path)
        {
            string result = "";
            string pageEncode = "GB2312";
            try
            {
                if (System.IO.File.Exists(path))
                {
                    using (FileStream fs = new FileStream(path, FileMode.Open))
                    {
                        using (StreamReader sr = new StreamReader(fs, System.Text.Encoding.GetEncoding(pageEncode)))
                        {
                            result = sr.ReadToEnd();
                        }
                    }
                }
            }
            catch (Exception)
            {
                return "";
            }

            return result;
        }

        public static int OccurNum(string pattern, string input)
        {
            try
            {
                return Regex.Matches(input, pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled).Count;
            }
            catch (Exception)
            {
                return 0;
            }
        }

        #endregion
    }
    public class ObjectId
    {
        private string _string;

        public ObjectId()
        {
        }

        public ObjectId(string value)
            : this(DecodeHex(value))
        {
        }

        internal ObjectId(byte[] value)
        {
            Value = value;
        }

        public static ObjectId Empty
        {
            get { return new ObjectId("000000000000000000000000"); }
        }

        public byte[] Value { get; private set; }

        public static ObjectId NewObjectId()
        {
            return new ObjectId { Value = ObjectIdGenerator.Generate() };
        }

        public static bool TryParse(string value, out ObjectId objectId)
        {
            objectId = Empty;
            if (value == null || value.Length != 24)
            {
                return false;
            }

            try
            {
                objectId = new ObjectId(value);
                return true;
            }
            catch (FormatException)
            {
                return false;
            }
        }

        protected static byte[] DecodeHex(string value)
        {
            if (string.IsNullOrEmpty(value))
                throw new ArgumentNullException("value");

            var chars = value.ToCharArray();
            var numberChars = chars.Length;
            var bytes = new byte[numberChars / 2];

            for (var i = 0; i < numberChars; i += 2)
            {
                bytes[i / 2] = Convert.ToByte(new string(chars, i, 2), 16);
            }

            return bytes;
        }

        public override int GetHashCode()
        {
            return Value != null ? ToString().GetHashCode() : 0;
        }

        public override string ToString()
        {
            if (_string == null && Value != null)
            {
                _string = BitConverter.ToString(Value)
                  .Replace("-", string.Empty)
                  .ToLowerInvariant();
            }

            return _string;
        }

        public override bool Equals(object obj)
        {
            var other = obj as ObjectId;
            return Equals(other);
        }

        public bool Equals(ObjectId other)
        {
            return other != null && ToString() == other.ToString();
        }

        public static implicit operator string(ObjectId objectId)
        {
            return objectId == null ? null : objectId.ToString();
        }

        public static implicit operator ObjectId(string value)
        {
            return new ObjectId(value);
        }

        public static bool operator ==(ObjectId left, ObjectId right)
        {
            if (ReferenceEquals(left, right))
            {
                return true;
            }

            if (((object)left == null) || ((object)right == null))
            {
                return false;
            }

            return left.Equals(right);
        }

        public static bool operator !=(ObjectId left, ObjectId right)
        {
            return !(left == right);
        }
    }

    internal static class ObjectIdGenerator
    {
        private static readonly DateTime Epoch =
          new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        private static readonly object _innerLock = new object();
        private static int _counter;
        private static readonly byte[] _machineHash = GenerateHostHash();
        private static readonly byte[] _processId =
          BitConverter.GetBytes(GenerateProcessId());

        public static byte[] Generate()
        {
            var oid = new byte[12];
            var copyidx = 0;

            Array.Copy(BitConverter.GetBytes(GenerateTime()), 0, oid, copyidx, 4);
            copyidx += 4;

            Array.Copy(_machineHash, 0, oid, copyidx, 3);
            copyidx += 3;

            Array.Copy(_processId, 0, oid, copyidx, 2);
            copyidx += 2;

            Array.Copy(BitConverter.GetBytes(GenerateCounter()), 0, oid, copyidx, 3);

            return oid;
        }

        private static int GenerateTime()
        {
            var now = DateTime.UtcNow;
            var nowtime = new DateTime(Epoch.Year, Epoch.Month, Epoch.Day,
              now.Hour, now.Minute, now.Second, now.Millisecond);
            var diff = nowtime - Epoch;
            return Convert.ToInt32(Math.Floor(diff.TotalMilliseconds));
        }

        private static byte[] GenerateHostHash()
        {
            using (var md5 = System.Security.Cryptography.MD5.Create())
            {
                var host = System.Net.Dns.GetHostName();
                return md5.ComputeHash(Encoding.Default.GetBytes(host));
            }
        }

        private static int GenerateProcessId()
        {
            var process = System.Diagnostics.Process.GetCurrentProcess();
            return process.Id;
        }

        private static int GenerateCounter()
        {
            lock (_innerLock)
            {
                return _counter++;
            }
        }
    }

    public class FileDBException : Exception
    {
        public FileDBException(string message)
            : base(message) { }

        public FileDBException(string message, Exception innerException)
            : base(message, innerException) { }
    }
}
